/*
 * @(#)Buffer.java 1.0 30 May 2007
 *
 * Copyright (c) 2005-2007 Soluciones Info Móviles S.A.C.
 * Av. Javier Prado Oeste 203 - Piso 13, Lima 27, Perú.
 * Todos los derechos reservados.
 */
package sim.utils;


/**
 * En esta clase se implementa el algoritmo para la administración de
 * memoria asignada dinamicamente. El algoritmo se basa en que la
 * memoria utilizada, conocida como sector, se puede reubicar al
 * principio de la memoria asignada no utilizada. La reasignación de un
 * nuevo bloque de memoria se da en el caso de que la memoria utilizada
 * sea igual a la memoria asignada (bloque).
 *
 * @author    Omar Quintana  (implementation)
 * @version   %I%, %G%
 * @since     1.0
 */
final public class Buffer {
    
    private static final int BEST_SIZE = 64;
    private static final int MIN_SIZE = 16;
    
    protected byte[] m_Chunk = null;  // Chunk de memoria asignada
    protected int m_Offset = 0;    // offset del primer byte que contiene datos.
    protected int m_Finish = 0;    // offset del último byte que contiene datos.
    
    /** 
     * Constructor.
     * 
     * @param count Longitud inicial del arreglo bytes.
     * 
     * @return Devuevel una referencia a un objeto de la clase Buffer.
     */
    public Buffer(int count){
        count = (count <  MIN_SIZE) ? Buffer.MIN_SIZE : count;
        m_Chunk = new byte[count];
    }

    /** 
     * Constructor.
     * 
     * 
     * @return Devuevel una referencia a un instancia de un objeto Buffer.
     */    
    public Buffer(){
        new Buffer(Buffer.BEST_SIZE);
    }
    
    /**
     * Retorna el arreglo de bytes utilizado.
     *
     * @return      bytes utilizados en el buffer
     */
    public final byte[] bytes() {
        return m_Chunk;
    }
    
    /**
     * Retorna la posición del primer byte activo
     *
     * @return      posición del primer byte activo
     */
    public int offset() {
        return m_Offset;
    }
    
    /**
     * Asocia un arreglo de bytes a buffer.
     *
     * @param buffer Nuevo arreglo de bytes válido (diferente de null).
     */
    public void attach(byte [] buffer){
        if (buffer != null) {
            m_Chunk = null; // Se supone Buffer tiene la única referencia, entonces bye bye ;-)
            m_Chunk = buffer;
            m_Offset = 0;
            m_Finish = 0;
        }
    }
    
    /**
     * Longitud del arreglo asignado.
     *
     * @return  Devuelve la longitud del buffer utilizado y no la longitud
     *          del sector de bytes utilizado.
     * @since   1.0
     */
    public int count() {
        return m_Chunk.length;
    }
    
    /**
     * Libera el sector de bytes utilizados.
     *
     * @since   1.0
     */
    public void clear(){
        m_Offset = 0;
        m_Finish = 0;
    }
    
    /** 
     * Reserva length bytes en el sector de bytes utilizados.
     * 
     * @param length longitud de bytes que se desea reservar.
     * 
     * @return Si tiene éxito, devuelve la posición del primer
     *         byte reservado; en caso contrario devuelve -1.
     */
    public int appendSpace(int length){
        int ret;
        int newlen;
        byte[] newChunk;
        
        if (length < 0) {
            return -1;
        }
        
        ret = -1;
        
        do {
	    // Si el buffer está vacío ...
            if (m_Offset == m_Finish) {
		// ... empezar a utilizarlo desde el principio
                clear();
            }
            
	    // Si hay espacio suficiente para almacenar la data ...
            if (m_Finish + length < count()) {
		// ... utilizarlo.
                ret = m_Finish;
                m_Finish += length;
                break;
            }
            
	    // Si el buffer esta practicamente vacio, pero la mayoria de la data
	    // esta al final de la region ...
            if (m_Offset > (count() / 2)) {
		// ... mover la data al principio y reintentar.
                Utils.memmove(m_Chunk, m_Offset, 0, length());
                m_Finish -= m_Offset;
                m_Offset = 0;
            }
	    else {
		// ... Asignar más espacio
		newlen = m_Chunk.length + length + Buffer.BEST_SIZE;
		newChunk = new byte[newlen];
		if (newChunk == null) {
		    // Paranoia!!! :-P
		    ret = -1;
		    break;
		}
		Utils.memmove(newChunk, 0, m_Chunk, 0, m_Finish);
		m_Chunk = newChunk;
		newChunk = null;
            }
        } while (ret < 0);
        
        return ret;
    }
    
    /**
     * Añade una porción de bytes al sector de bytes utilizado.
     *
     * @param data Porción de bytes que se desea añadir.
     * @param start Posición del primer byte que se desea añadir.
     * @param length Longitud de bytes que se desea añadir.
     */
    public void append(final byte[] data, int start, int length){
        int p;
        p = appendSpace(length);
        if (p > -1) {
            Utils.memmove(m_Chunk, p, data, start, length);
        }
    }
    
    /**
     * Añade una porción de bytes al sector de bytes utilizado,
     * empezando desde CERO.
     *
     * @param data   Porción de bytes que se desea añadir.
     * @param length Longitud de bytes que se desea añadir.
     * @since   1.0
     */
    public void append(byte[] data, int length){
        append(data, 0, length);
    }
    
    public void append(byte[] data){
        append(data, 0, data.length);
    }
    
    public void append(byte b) {
        int p;
        p = appendSpace(1);
        if (p > -1) {
            m_Chunk[p] = b;
        }
    }
    
    /**
     * Longitud del sector de bytes utilizado.
     *
     * @return  Devuelve la longitud del sector de bytes utilizados.
     * @since   1.0
     */
    public int length(){
        return m_Finish - m_Offset;
    }
        
    /**
     * Rellena todo le arreglo de bytes asignado con un bytes especificado.
     *
     * @param b Byte que se desea utilizar.
     */
    public void fill(byte b){
        for (int i = 0; i < m_Chunk.length; i++){
            m_Chunk[i] = b;
        }
    }
    
    /**
     * Invalida los N primeros bytes del sector de bytes utilizados.
     *
     * @param length Longitud de bytes que se desea invalidar.
     */
    public void consume(int length){
        if (length > 0) {
            m_Offset += Math.min(length, length());
        } else {
            m_Offset -=  Math.min(-length, m_Offset);
        }
    }
    
    /**
     * Invalida los N últimos bytes del sector de bytes utilizados.
     *
     * @param length Longitud de bytes que se desea invalidar.
     */
    public void consumeEnd(int length){
        if (length > 0) {
            m_Finish -= Math.min(length, length());
        }
    }
    
}
